namespace Wilco.CodeHilighter.Engine.SyntaxHighlighting
{
    using System;
    using System.Collections.Specialized;
    using System.Drawing;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Xml.Linq;

    /// <summary>
    /// Represents an CSS scanner.
    /// </summary>
    public class CssScanner : ScannerBase
    {
        #region Fields

        private CssAttributeNameNode cssAttributeNameNode;
        private CssAttributeValueNode cssAttributeValueNode;
        private CssAttributeNameNode cssSelectorNameNode;
        private int state;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of an <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CssScanner"/> class.
        /// </summary>
        public CssScanner()
            : base(null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of an <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CssScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        public CssScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
            : base(tokenizer, scannerResult)
        {
            this.cssSelectorNameNode = new CssAttributeNameNode();
            this.cssSelectorNameNode.ForeColor = Color.Brown;//should be maroon!
            this.cssAttributeNameNode = new CssAttributeNameNode();
            this.cssAttributeNameNode.ForeColor = Color.Red;
            this.cssAttributeValueNode = new CssAttributeValueNode();
            this.cssAttributeValueNode.ForeColor = Color.Blue;
            this.SetID("CssScanner");
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new CssScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XElement element = (XElement)state;
            this.LoadNode(element, this.cssSelectorNameNode, "SelectorNameNode");
            this.LoadNode(element, this.cssAttributeNameNode, "AttributeNameNode");
            this.LoadNode(element, this.cssAttributeValueNode, "AttributeValueNode");
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            this.state = 0;
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            //XDocument document = (XDocument)container;
            XElement element = (XElement)base.SaveState(container);

            // Save settings.
            XElement nodeRootElement = new XElement("nodes");
            element.Add(nodeRootElement);

            this.StoreNode(nodeRootElement, this.cssSelectorNameNode, "SelectorNameNode");
            this.StoreNode(nodeRootElement, this.cssAttributeNameNode, "AttributeNameNode");
            this.StoreNode(nodeRootElement, this.cssAttributeValueNode, "AttributeValueNode");

            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation will generally have a reference to a 
        /// <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!this.Enabled)
            {
                if (this.Child != null)
                {
                    this.Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                char ch = token[0];

                if (!char.IsWhiteSpace(ch))
                {
                    // Match identifier(s).
                    if (this.state == 0)
                    {
                        if (ch == '{')
                        {
                            this.state = 1;
                            isMatch = true;
                        }
                        else
                        {
                            isMatch = this.MatchSelector();
                        }
                    }
                    else
                    {
                        if (ch == '}')
                        {
                            this.state = 0;
                            isMatch = true;
                        }
                        else
                        {
                            if (this.state == 1)
                            {
                                isMatch = this.MatchStyleName();
                            }
                            else if (this.state == 2)
                            {
                                isMatch = this.MatchStyleValue();
                            }
                        }
                    }
                }

                if (!isMatch)
                {
                    if (this.Child != null)
                    {
                        this.Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private XElement CreateSetting(string name, string value)
        {
            return new XElement("setting",value, new XAttribute("name", name));
        }

        /// <summary>
        /// Loads the information for a node.
        /// </summary>
        /// <param name="element">The <see cref="System.Xml.XmlElement"/> which contains the information about the node.</param>
        /// <param name="node">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.INode"/> implementation class for which the information will be set.</param>
        /// <param name="name">The name of the node.</param>
        private void LoadNode(XElement element, INode node, string name)
        {
            XElement nodeElement = (XElement)Helper.GetElemementByAttributeName(element, "nodes", "node", "name", name);
            node.BackColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "BackColor"));
            node.ForeColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "ForeColor"));
            node.Font = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "Font");
            node.NavigateUrl = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "NavigateUrl");
        }

        private bool MatchSelector()
        {
            int start = this.Tokenizer.Position;

            char ch = this.Tokenizer.Source[this.Tokenizer.Position];
            while (char.IsLetterOrDigit(ch) || ch == '-' || ch == '#')
            {
                this.Tokenizer.MoveNext();
                ch = this.Tokenizer.Source[this.Tokenizer.Position];
            }

            if (start < this.Tokenizer.Position)
            {
                this.ScannerResult.Add(new Occurrence(start, this.Tokenizer.Position - start, this.cssSelectorNameNode));

                return true;
            }

            return false;
        }

        private bool MatchStyleName()
        {
            int start = this.Tokenizer.Position;

            char ch = this.Tokenizer.Source[this.Tokenizer.Position];
            while (char.IsLetterOrDigit(ch) || ch == '-')
            {
                this.Tokenizer.MoveNext();
                ch = this.Tokenizer.Source[this.Tokenizer.Position];
            }

            if (start < this.Tokenizer.Position)
            {
                this.ScannerResult.Add(new Occurrence(start, this.Tokenizer.Position - start, this.cssAttributeNameNode));

                this.state = 2;
                return true;
            }

            return false;
        }

        private bool MatchStyleValue()
        {
            int end = this.Tokenizer.Source.IndexOf(";", this.Tokenizer.Position);
            if (end > -1)
            {
                this.ScannerResult.Add(new Occurrence(this.Tokenizer.Position, end - this.Tokenizer.Position, this.cssAttributeValueNode));
                this.Tokenizer.MoveTo(end);
                this.state = 1;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Stores a node.
        /// </summary>
        /// <param name="nodeRootElement">The element which will hold the node's settings.</param>
        /// <param name="node">The node which should be represented.</param>
        /// <param name="name">The name of the node.</param>
        private void StoreNode(XElement nodeRootElement, INode node, string name)
        {
            XElement nodeElement = new XElement("node");
            nodeElement.SetAttributeValue("name", name);
            nodeRootElement.Add(nodeElement);

            XElement settingRootElement = new XElement("settings");
            nodeElement.Add(settingRootElement);

            settingRootElement.Add(this.CreateSetting("BackColor", ColorTranslator.ToHtml(node.BackColor)));
            settingRootElement.Add(this.CreateSetting("ForeColor", ColorTranslator.ToHtml(node.ForeColor)));
            settingRootElement.Add(this.CreateSetting("Font", node.Font));
            settingRootElement.Add(this.CreateSetting("NavigateUrl", node.NavigateUrl));
        }

        #endregion Methods
    }
}